/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.newupbank.many2many;

// import com.newupbank.bgc.basic.util.json.BasicJsonUtil;
import com.alibaba.fastjson.JSON;
import com.newupbank.xmlparser.XmlParserConfig;
import jodd.util.StringUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.util.*;

/**
 * 0.0.3 空行处理 使用方法： select MulXmlParser1( 字段/xml内容 , "父节点", "节点1", "节点2", "节点2"
 * ) from table ; 只解析多行节点，如果不存在则为空 ， 多行的只能使用一个父节点
 * <p>
 * 0.0.4 注意输入的第二个参数appid类型为N
 *
 * @author lifanhong
 */
public class XmlParser extends GenericUDTF {

    // public static final BasicJsonUtil BJU = BasicJsonUtil.getInstance();

    @Override
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
        List<?> inputFields = argOIs.getAllStructFieldRefs();
        List<String> fieldName = new ArrayList<>();
        List<ObjectInspector> resType = new ArrayList<>();

        for (int j = 0; j < (inputFields.size() - 1); j++) {
            // StructField sf = (StructField) inputFields.get(j);
            fieldName.add("c" + j);
            resType.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
        }
        // 返回分别为列名 和 列类型
        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldName, resType);
    }

    @Override
    public void process(Object[] objects) throws HiveException {
        if (objects.length < 2) {
            forward(objects);
            return;
        }
        String strXml = null;
        Document document = null;
        SAXReader reader = null;
        StringReader stringReader = null;
        List<XmlParserConfig> configList = null;
        Map<String, String> one2nMap = null;
        String fatherNode = null;
        List<Node> nodeList = null;
        List<XmlParserConfig> xpcNONList = null;
        try {
            // 初始化
            strXml = Objects.toString(objects[0]);
            if (StringUtil.isEmpty(strXml)) {
                return;
            }
            reader = new SAXReader();
            stringReader = new StringReader(strXml);
            document = reader.read(stringReader);

            // 转为javabean
            configList = toConfigList(objects);

            //#######以下为NN的逻辑#######################
            //1.对入参进行分类[NN,非NN]
            List<Object> objectNNList = new ArrayList<>();//NN
            List<Object> objectNONList = new ArrayList<>();//非NN

            objectNNList.add(objects[0]);//xml
            //objectNNList.add(objects[1]);//appid

            objectNONList.add(objects[0]);//xml
            //objectNONList.add(objects[1]);//appid

            for (int i = 0; i < configList.size(); i++) {
                if (i == 0) {
                    objectNNList.add(objects[i + 1]);
                    if (!"NN".equals(configList.get(i).getType())) {
                        objectNONList.add(objects[i + 1]);
                    }
                } else {
                    if ("NN".equals(configList.get(i).getType())) {
                        objectNNList.add(objects[i + 1]);
                    } else {//非NN
                        objectNONList.add(objects[i + 1]);
                    }
                }


            }
            //NN objectsNN
            Object[] objectsNN = new Object[objectNNList.size()];
            objectNNList.toArray(objectsNN);
            if (objectsNN.length > 2) {
                List<String[]> arrNNList = parseNN(objectsNN, document);
                for (String[] arrNN : arrNNList) {
                    //JudgingEmpty(arrNN);
                    System.out.println(Arrays.toString(arrNN));
                }
            }
            //############以上为NN的逻辑################

            //非NN objectsNON
            Object[] objectsNON = new Object[objectNONList.size()];
            objectNONList.toArray(objectsNON);
            xpcNONList = toConfigList(objectsNON);
            // 先1:N的单个字段
            one2nMap = getSingleNodeText(xpcNONList, document);
            // 获取公共节点
            fatherNode = getCommonNode(xpcNONList);
            // 如果有公共节点
            if (!StringUtils.isEmpty(fatherNode)) {
                // 先获取公共节点,然后生成
                nodeList = document.selectNodes(fatherNode);
                int row = 1;
                if (nodeList != null && !nodeList.isEmpty()) {
                    for (Node node : nodeList) {
                        // 生成数据数组
                        String[] data = geneRow(xpcNONList, one2nMap, fatherNode, node, row);
                        System.out.println(Arrays.toString(data));
                        //JudgingEmpty(data);

                    }
                } else {
                    String[] data = geneRow(xpcNONList, one2nMap, fatherNode);
                    System.out.println(Arrays.toString(data));
                    //JudgingEmpty(data);
                }
            } else if (!one2nMap.isEmpty()) {
                String[] data = geneRow(xpcNONList, one2nMap, fatherNode);
                System.out.println(Arrays.toString(data));
                //JudgingEmpty(data);
            }

        } catch (Exception ex) {
            System.err.println("xpcNONList=" + xpcNONList);
            System.err.println("one2nMap=" + one2nMap);
            System.err.println("fatherNode=" + fatherNode);
            System.err.println("nodeList=" + nodeList);
            ex.printStackTrace();
            throw new HiveException(ex);
        } finally {
            if (stringReader != null) {
                stringReader.close();
                stringReader = null;
            }
            document = null;
            reader = null;
        }
    }

    //对入参进行分类[NN,list],[N,list].[1N,list]
    public Map<String, List<XmlParserConfig>> getCatalogMap(List<XmlParserConfig> configList) {
        Map<String, List<XmlParserConfig>> catalogMap = new HashMap<>();
        for (XmlParserConfig xpc : configList) {
            if (!catalogMap.containsKey(xpc.getType())) {//不包含key
                List<XmlParserConfig> xpcList = new ArrayList<>();
                xpcList.add(xpc);
                catalogMap.put(xpc.getType(), xpcList);
            } else {//包含key
                catalogMap.get(xpc.getType()).add(xpc);
            }

        }
        return catalogMap;
    }

    public String[] geneRow(List<XmlParserConfig> configList, Map<String, String> one2nMap, String fatherNode,
                            Node node, int rowNum) throws Exception {
        String[] ret = new String[configList.size()];
        String xs = null;
        String nodes = null;
        try {
            for (int i = 0; i < configList.size(); i++) {
                XmlParserConfig xpc = configList.get(i);
                if (XmlParserConfig.Type1N.equals(xpc.getType())) {
                    ret[i] = one2nMap.get(xpc.getTag());
                } else if (XmlParserConfig.TypeRowNum.equals(xpc.getType())) {
                    ret[i] = rowNum + "";
                } else if (XmlParserConfig.TypeN.equals(xpc.getType())) {
                    nodes = xpc.getSrc();
                    if (nodes.length() <= fatherNode.length()) {
                        xs = nodes;
                    } else {
                        xs = nodes.substring(fatherNode.length() + 1);
                    }
                    if (StringUtils.isEmpty(xs)) {
                        ret[i] = "";
                        continue;
                    }
                    // String ns = StringUtil.substring(nodes,
                    // nodes.lastIndexOf("/") + 1,
                    // nodes.length());
                    Node nm = node.selectSingleNode(xs);
                    if (nm != null) {
                        ret[i] = nm.getText();
                        continue;
                    } else {
                        ret[i] = "";
                        continue;
                    }
                } else {
                    throw new Exception(" not super type " + xpc.getType());
                }
            }
        } catch (Exception ex) {
            System.err.println("nodes=" + nodes);
            System.err.println("xs=" + xs);
            ex.printStackTrace();
            throw ex;
        }
        return ret;
    }

    //解析NN
    public List<String[]> parseNN(Object[] objects, Document document) throws Exception {
        Map<Integer, List<XmlParserConfig>> depthMap;//2.统计每个 src中路径/有多少。即标签层级
        //2.1将src转为XmlParserConfig(最初的xpcList)
        ArrayList<XmlParserConfig> xpcList = new ArrayList<>();
        for (int i = 1; i < objects.length; i++) {
            String tmps = Objects.toString(objects[i]);
            XmlParserConfig xpc = JSON.parseObject(tmps,XmlParserConfig.class);
            xpcList.add(xpc);
        }

        //2.2存放标签深度，对应的标签集合(key无序)
        depthMap = new HashMap<>();
        char ch = '/';
        for (int i = 0; i < xpcList.size(); i++) {
            String src = xpcList.get(i).getSrc();
            char[] srcChars = src.toCharArray();
            int numChar = 0;
            for (int j = 0; j < srcChars.length; j++) {
                if (srcChars[j] == ch) {
                    numChar++;
                }
            }

            if (depthMap.containsKey(numChar)) {
                depthMap.get(numChar).add(xpcList.get(i));
            } else {
                List<XmlParserConfig> xmpList1 = new ArrayList<>();
                xmpList1.add(xpcList.get(i));
                depthMap.put(numChar, xmpList1);
            }

        }


        //2.3对深度集合的key排序
        List<Integer> keyList = new ArrayList<>(depthMap.keySet());
        Collections.sort(keyList);

        //2.4找后缀的方法
        List<List> suffixMList = getPubSuffix(depthMap, keyList);

        //4存放最终的结果
        List<String[]> resultList = new ArrayList<>();

        //3.找关联关系
        //#######倒序遍历
   /*     TempPojo tempPojo = new TempPojo();
        List<List<XmlParserConfig>> tmpList = new ArrayList<>();
        for (int i = keyList.size()-1; i >=0 ; i--) {
           tmpList.add(depthMap.get(keyList.get(i)));
        }
        tempPojo.setList(tmpList);*/

        //第一层
        XmlParserConfig xpc0 = depthMap.get(keyList.get(0)).get(0);
        Node node0 = document.selectSingleNode(xpc0.getSrc());

        //第三层（根据第三层找第二层）
        int diff = keyList.get(2) - keyList.get(1);
        List<XmlParserConfig> xpcList3 = depthMap.get(keyList.get(2));
        String publicPrefix3 = getPublicPrefix(xpcList3);
        List<Element> elements = document.selectNodes(publicPrefix3);
        for (Element element3 : elements) {
            List<String> arrayList = new ArrayList<>();
            arrayList.add(node0.getText());

            //二级节点属性
            Element element2 = element3.getParent();
            for (int i = keyList.get(2); i > keyList.get(1) + 1; i--) {
                element2 = element2.getParent();
            }
            List<String> suffixList2 = suffixMList.get(1);
            for (String str2 : suffixList2) {
                arrayList.add(element2.attributeValue(str2));
            }

            //三级节点属性
            List<String> suffixList3 = suffixMList.get(2);
            for (String str3 : suffixList3) {
                arrayList.add(element3.attributeValue(str3));
            }

            String[] arrStr = new String[arrayList.size()];
            arrayList.toArray(arrStr);
            //System.out.println(Arrays.toString(arrStr));
            resultList.add(arrStr);

        }
        return resultList;
    }

    //NN找公共后缀
    public List<List> getPubSuffix(Map<Integer, List<XmlParserConfig>> depthMap, List<Integer> keyList) {
        List<List> suffixMList = new ArrayList<>();
        for (int i = 0; i < keyList.size(); i++) {
            List<XmlParserConfig> xpcList = depthMap.get(keyList.get(i));
            List<String> suffixList = new ArrayList<>();
            for (int j = 0; j < xpcList.size(); j++) {
                String src = xpcList.get(j).getSrc();
                suffixList.add(src.substring(src.lastIndexOf("@") + 1));
            }
            suffixMList.add(suffixList);
        }
        return suffixMList;
    }


    //NN找公共前缀
    public String getPublicPrefix(List<XmlParserConfig> xpcList2) throws Exception {
        List<String> srcList = new ArrayList<>();
        for (XmlParserConfig xpc : xpcList2) {
            srcList.add(xpc.getSrc());
        }
        String[] srcArr = new String[srcList.size()];

        srcList.toArray(srcArr);
        String commonPrefix = StringUtils.getCommonPrefix(srcArr);
        if (StringUtils.isEmpty(commonPrefix) || "/".equals(commonPrefix)) {
            throw new Exception(" not have  the common node ");
        }
        if (commonPrefix.contains("@")) {
            commonPrefix = commonPrefix.substring(0, commonPrefix.lastIndexOf("@") - 1);
        } else if (commonPrefix.endsWith("/")) {
            commonPrefix = commonPrefix.substring(0, commonPrefix.length() - 1);
        }
        return commonPrefix;
    }


    public String[] geneRow(List<XmlParserConfig> configList, Map<String, String> one2nMap, String fatherNode)
            throws Exception {
        String[] ret = new String[configList.size()];
        for (int i = 0; i < configList.size(); i++) {
            XmlParserConfig xpc = configList.get(i);
            if (XmlParserConfig.Type1N.equals(xpc.getType())) {
                ret[i] = one2nMap.get(xpc.getTag());
            } else if (XmlParserConfig.TypeRowNum.equals(xpc.getType())) {
                ret[i] = "1";
            } else if (XmlParserConfig.TypeN.equals(xpc.getType())) {
                ret[i] = "";
                continue;
            } else {
                throw new Exception(" not super type " + xpc.getType());
            }
        }
        return ret;
    }

    public String getCommonNode(List<XmlParserConfig> configList) throws Exception {
        // TODO Auto-generated method stub
        String ret = "";
        // StringUtils.getCommonPrefix(strs);
        List<String> nList = new ArrayList<>(configList.size());
        for (int i = 0; i < configList.size(); i++) {
            XmlParserConfig xpc = configList.get(i);
            if (XmlParserConfig.TypeN.equals(xpc.getType())) {
                nList.add(xpc.getSrc());
            }
        }
        if (nList.isEmpty()) {
            return ret;
        }
        // 获取前置内容
        String[] sts = new String[nList.size()];
        sts = nList.toArray(sts);
        ret = StringUtils.getCommonPrefix(sts);
        if (StringUtils.isEmpty(ret) || "/".equals(ret)) {
            throw new Exception(" not have  the common node ");
        }
        if (ret.endsWith("@")) {
            ret = ret.substring(0, ret.length() - 1);
        }
        if (ret.endsWith("/")) {
            ret = ret.substring(0, ret.length() - 1);
        }
        return ret;
    }

    private List<XmlParserConfig> toConfigList(Object[] objects) throws IOException {
        // TODO Auto-generated method stub
        List<XmlParserConfig> configList = new ArrayList<>(objects.length - 1);
        for (int i = 1; i < objects.length; i++) {
            String tmps = Objects.toString(objects[i]);
            // XmlParserConfig xpc = BJU.toJavaBean(tmps, XmlParserConfig.class);
            XmlParserConfig xpc = JSON.parseObject(tmps, XmlParserConfig.class);
            configList.add(xpc);
        }
        return configList;
    }

    private Map<String, String> getSingleNodeText(List<XmlParserConfig> configList, Document document) {
        // TODO Auto-generated method stub
        Map<String, String> one2nMap = new HashMap<>(configList.size());
        for (XmlParserConfig xpc : configList) {
            if (XmlParserConfig.Type1N.equals(xpc.getType())) {
                Node node = document.selectSingleNode(xpc.getSrc());
                if (node != null) {
                    one2nMap.put(xpc.getTag(), node.getText());
                }
            }
        }
        return one2nMap;
    }

    public void processOld(Object[] objects) throws HiveException {
        List<Element> nodeList = new ArrayList<>();
        List<String> valueList = new ArrayList();
        List<Element> node2List = new ArrayList<>();
        List<String> value2List = new ArrayList();
        List<Element> fileLength = new ArrayList();
        List<String> arrayList = new ArrayList();
        List<String[]> resultList = new ArrayList();
        List<String> valueSize = new ArrayList();

        String strXml = Objects.toString(objects[0]);
        Document document = null;
        SAXReader reader = new SAXReader();
        StringReader stringReader = new StringReader(strXml);
        try {
            document = reader.read(stringReader);

        } catch (DocumentException e) {
            e.printStackTrace();
            throw new HiveException(e);
        }
        if (document != null) {
            nodeList = document.selectNodes(objects[1].toString());
            for (Element element : nodeList) {
                valueList.add(element.getText());
            }
            fileLength = document.selectNodes(objects[2].toString());
            int lineNum = 0;
            if (fileLength.size() > 0) {
                for (Element element : fileLength) {
                    valueSize.add(element.getText());
                }
                lineNum = valueSize.size();
            }
            for (int k = 0; k < lineNum; k++) {
                arrayList.clear();
                arrayList.add(valueList.get(0));
                for (int i = 2; i < objects.length - 1; i++) {
                    node2List.clear();
                    value2List.clear();
                    node2List = document.selectNodes(objects[i].toString());
                    if (node2List.size() > 0) {
                        for (Element element : node2List) {
                            value2List.add(element.getText());
                        }
                        arrayList.add(value2List.get(k));
                    }
                }
                arrayList.add((k + 1) + "");
                String[] array = arrayList.toArray(new String[arrayList.size()]);
                resultList.add(array);
                forward(resultList.get(0));
                resultList.clear();
            }

        }

    }

    @Override
    public void close() throws HiveException {

    }

    // 判断(除AppId外)字段值是否全为空，全为空则不返回数据
    public void JudgingEmpty(String[] data) throws HiveException {
        int b = 0;
        for (int i = 1; i < data.length; i++) {
            if (data[i].length() > 0) {
                b = b + 1;
            } else {
                continue;
            }
        }
        if (b > 0) {
            System.out.println("b>0  " + b);
//            forward(data);
        } else {
            System.out.println("不返回数据");
            return;
        }
    }


    public static void main(String[] args) {
        XmlParser xmlp = new XmlParser();
    /*    String xml = "<Application AppId=\"jksidhaish8796987sadfasdfjd100842\" SourceCode=\"newupBank\">"
                + "<ScoreModelReturnInfo FinalScore=\"462.0\" ScoreModelName=\"申请评分模型1\" something=\"aaaa\">		"
                + "<ScoredCharacteristic BaselineScore=\"0.2\" BinLabel=\"男1\" />	<ScoredCharacteristic "
                + "BaselineScore=\"0.0\" BinLabel=\"男\" />		"
                + "</ScoreModelReturnInfo>	<ReturnInfoVariable workingYears=\"30.0\" workingYears1=\"30.0\" workingYears2=\"30.0\"/>	"
                + "<ScoreModelReturnInfo FinalScore=\"461.0\" ScoreModelName=\"申请评分模型\" something=\"bbbb\">		"
                + "<ScoredCharacteristic BaselineScore=\"0.1\" BinLabel=\"女\" /></ScoreModelReturnInfo >"
                + "<ScoreModelReturnInfo FinalScore=\"451.0\" ScoreModelName=\"申请评分模型5\" something=\"ccccc\">		"
                + "<ScoredCharacteristic BaselineScore=\"0.5\" BinLabel=\"女5\" /></ScoreModelReturnInfo>"
                + "</Application>";
        System.out.println(xml);
        String arr1 = "{\"src\":\"/Application/@AppId\",\"tag\":\"c1\",\"type\":\"N\"}";
        String arr2 = "{\"src\":\"/Application/ScoreModelReturnInfo/@ScoreModelName\",\"tag\":\"c1\",\"type\":\"NN\"}";
        String arr3 = "{\"src\":\"/Application/ScoreModelReturnInfo/@FinalScore\",\"tag\":\"c1\",\"type\":\"NN\"}";
        String arr4 = "{\"src\":\"/Application/ScoreModelReturnInfo/@something\",\"tag\":\"c1\",\"type\":\"NN\"}";
        String arr5 = "{\"src\":\"/Application/ScoreModelReturnInfo/ScoredCharacteristic/@BinLabel\",\"tag\":\"c1\",\"type\":\"NN\"}";
        String arr6 = "{\"src\":\"/Application/ScoreModelReturnInfo/ScoredCharacteristic/@BaselineScore\",\"tag\":\"c1\",\"type\":\"NN\"}";
        //String arr2 = "{\"src\":\"/Application/ReturnInfoVariable/@workingYears\",\"tag\":\"c1\",\"type\":\"N\"}";
        //String arr3 = "{\"src\":\"/Application/ReturnInfoVariable/@workingYears1\",\"tag\":\"c1\",\"type\":\"N\"}";
        //String arr4 = "{\"src\":\"/Application/ReturnInfoVariable/@workingYears2\",\"tag\":\"c1\",\"type\":\"N\"}";
        Object[] obj = new Object[7];
        obj[0] = xml;
        obj[1] = arr1;
        obj[2] = arr2;
        obj[3] = arr3;
        obj[4] = arr4;
        obj[5] = arr5;
        obj[6] = arr6;*/


        try {
            //读取xml
            BufferedReader br = new BufferedReader(new FileReader("data/UDTF数据源解析.xml"));
            StringBuffer sb = new StringBuffer();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
            String xmlStr = sb.toString();
            Object[] obj = new Object[10];
            obj[0] = xmlStr;
            obj[1] = "{\"src\":\"/Application/@AppId\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[2] = "{\"src\":\"/Application/ScoreModelReturnInfo/@AssociationID\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[3] = "{\"src\":\"/Application/ScoreModelReturnInfo/@ScoreModelName\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[4] = "{\"src\":\"/Application/ScoreModelReturnInfo/@FinalScore\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[5] = "{\"src\":\"/Application/ScoreModelReturnInfo/ScoredCharacteristic/@CharacteristicName\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[6] = "{\"src\":\"/Application/ScoreModelReturnInfo/ScoredCharacteristic/@BinLabel\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[7] = "{\"src\":\"/Application/ScoreModelReturnInfo/ScoredCharacteristic/@BaselineScore\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[8] = "{\"src\":\"/Application/ScoreModelReturnInfo/ScoredCharacteristic/@PartialScore\",\"tag\":\"c1\",\"type\":\"NN\"}";
            obj[9] = "{\"src\":\"/Application/ScoreModelReturnInfo/ScoredCharacteristic/@MaxBinScore\",\"tag\":\"c1\",\"type\":\"NN\"}";
            //调用process方法
            xmlp.process(obj);
        } catch (HiveException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
